package cn.kinoko.aspect;

import cn.kinoko.error.TryLockFailException;
import cn.kinoko.utils.SpelUtil;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import cn.kinoko.annotation.DistributedLock;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.DefaultParameterNameDiscoverer;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.Expression;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;

/**
 * 分布式锁切面
 *
 * @author kinoko
 */
@Aspect
@Component
public class DistributedLockAspect {
    @Autowired
    private RedissonClient redissonClient;

    @Around("@annotation(distributedLock)")
    public Object around(ProceedingJoinPoint joinPoint, DistributedLock distributedLock) throws Throwable {
        // 获取redisKey
        String lockName = SpelUtil.getRedisKey(joinPoint, distributedLock.key(), distributedLock.params());
        Object target;
        try {
            // 获取可重入锁
            RLock lock = redissonClient.getLock(lockName);
            // 尝试加锁
            boolean success = distributedLock.autoRelease() ?
                    // 启用看门狗机制，默认30s加锁，业务超时自动续期
                    lock.tryLock() :
                    // 指定加锁时间，到时间自动释放
                    lock.tryLock(distributedLock.waitTime(), distributedLock.expire(), distributedLock.timeUnit());
            // 加锁失败返回msg
            if (!success) {
                throw new TryLockFailException(distributedLock.errorDesc());
            }
            // 执行业务方法
            target = joinPoint.proceed();
        }  finally {
            RLock lock = redissonClient.getLock(lockName);
            // 若是当前线程持有则释放锁
            if (lock.isHeldByCurrentThread()) {
                lock.unlock();
            }
        }
        return target;
    }

}